package appapi.config;

import appapi.Vo.AppVo.AppLoginVo;
import appapi.common.ResponseBean;
import appapi.entity.Zhongjiao.SysLogEntity;
import appapi.entity.basic.UserInfo;
import appapi.model.ResponseModel;
import appapi.service.ISysLogService;
import appapi.utils.HttpContextUtils;
import appapi.utils.IPUtils;
import appapi.utils.JwtUtil;
import appapi.utils.List2MapUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

@Aspect
@Component
@Slf4j
public class LogAspect {
    @Autowired
    private ISysLogService iSysLogService;

    /**
     * @annotation(MyLog类的路径) 在idea中，右键自定义的MyLog类-> 点击Copy Reference
     */
    @Pointcut("@annotation(appapi.config.MyLog)")
    public void logPointCut() {
        log.info("------>配置织入点");
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) throws Exception {
        handleLog(joinPoint, result, null);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) throws Exception {
        handleLog(joinPoint, null, e);
    }

    private void handleLog(final JoinPoint joinPoint, Object result, final Exception e) throws Exception {


        SysLogEntity operLog = new SysLogEntity();
        ResponseBean resultData = (ResponseBean) result;
        // 获得MyLog注解
        MyLog controllerLog = getAnnotationLog(joinPoint);
        if (controllerLog == null) {
            return;
        }
        String title = controllerLog.title();
        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();

        Map<String, Object> data = new HashMap<>();
        if (request.getRequestURI().contains("app")&&title.contains("登录")){
            List<AppLoginVo> appData = (List<AppLoginVo>) resultData.getData();
            if (appData!=null){
                AppLoginVo vo = appData.get(0);
                data = List2MapUtil.setConditionMap(vo);
                data.put("staffAccount",vo.getUserPhone());
                data.put("staffName",vo.getUserName());
            }
        }else if (!(resultData.getData() instanceof Map)){
            data = List2MapUtil.setConditionMap(resultData.getData());
        }else {
            data = (Map<String, Object>) resultData.getData();
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();

        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            String params = "";
            for (int i = 0; i < args.length; i++) {
                params += "  " + paramNames[i] + ": " + args[i];
            }
            operLog.setRequestParam(params);
        }
        operLog.setMethod(className + "." + methodName + "()");

        //日志类型 0=登录日志,1=操作日志,2=异常日志,3=告警日志'
        if (title.contains("登录")) {

            operLog.setOperateAccount(resultData.getCode() != 200 ? "" : data.get("staffAccount").toString());
            operLog.setOperateName(resultData.getCode() != 200 ? "" : data.get("staffName").toString());
            operLog.setLogType(0);

        } else {
            operLog.setLogType(1);
            //拿到操作人员的账号与名称,现在暂时存入的手机号
            UserInfo token = JwtUtil.getUserInfo(request.getHeader("token"));
            operLog.setOperateAccount(token.getMobile());
            operLog.setOperateName(token.getUsername());
        }
        // 操作状态（0正常 1异常）
        operLog.setOperateResult(0);
        // 操作时间
        operLog.setOperateTime(new Date());

        //code返回值为-1
        if (resultData.getCode() != 200) {
            operLog.setOperateResult(1);
            operLog.setErrorMsg(resultData.getMessage());
            operLog.setLogType(2);
        }
        

        //如果有异常则捕获返回
        if (e != null) {
            operLog.setOperateResult(1);
            operLog.setErrorMsg(e.getMessage());
            operLog.setLogType(2);
        }


        // 设置IP地址
        operLog.setIp(IPUtils.getIpAddr(request));
        // 设置uri
        operLog.setUri(request.getRequestURI());
        //操作类别
        operLog.setOperatorType(operLog.getUri().contains("app") ? 2 : 1);

        //请求方式
        operLog.setRequestMethod(request.getMethod());
        // 处理注解上的参数
        getControllerMethodDescription(joinPoint, controllerLog, operLog);

        //暂不用
        //operLog.setResult(JSONObject.toJSON(result).toString());

        // 保存数据库
        iSysLogService.saveSysLog(operLog);
    }

    /**
     * 是否存在注解，如果存在就获取，不存在则返回null
     *
     * @param joinPoint
     * @return
     */
    private MyLog getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(MyLog.class);
        }
        return null;
    }

    /**
     * 获取Controller层上MyLog注解中对方法的描述信息
     *
     * @param joinPoint 切点
     * @param myLog     自定义的注解
     * @param operLog   操作日志实体类
     */
    private void getControllerMethodDescription(JoinPoint joinPoint, MyLog myLog, SysLogEntity operLog) {

        operLog.setOperateType(myLog.businessType().ordinal());
        // 设置模块标题，eg:登录
        operLog.setBizModule(myLog.title());

        // 对方法上的参数进行处理，处理完：userName=xxx,password=xxx
        // String optParam = getAnnotationValue(joinPoint, myLog.optParam());
        //operLog.setOptParam(optParam);

    }

    /**
     * 对方法上的参数进行处理
     *
     * @param joinPoint
     * @param name
     * @return
     */
    private String getAnnotationValue(JoinPoint joinPoint, String name) {
        String paramName = name;
        // 获取方法中所有的参数
        Map<String, Object> params = getParams(joinPoint);
        // 参数是否是动态的:#{paramName}
        if (paramName.matches("^#\\{\\D*\\}")) {
            // 获取参数名,去掉#{ }
            paramName = paramName.replace("#{", "").replace("}", "");
            // 是否是复杂的参数类型:对象.参数名
            if (paramName.contains(".")) {
                String[] split = paramName.split("\\.");
                // 获取方法中对象的内容
                Object object = getValue(params, split[0]);
                // 转换为JsonObject
                JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
                // 获取值
                Object o = jsonObject.get(split[1]);
                return String.valueOf(o);
            } else {// 简单的动态参数直接返回
                StringBuilder str = new StringBuilder();
                String[] paraNames = paramName.split(",");
                for (String paraName : paraNames) {

                    String val = String.valueOf(getValue(params, paraName));
                    // 组装成 userName=xxx,password=xxx,
                    str.append(paraName).append("=").append(val).append(",");
                }
                // 去掉末尾的,
                if (str.toString().endsWith(",")) {
                    String substring = str.substring(0, str.length() - 1);
                    return substring;
                } else {
                    return str.toString();
                }
            }
        }
        // 非动态参数直接返回
        return name;
    }

    /**
     * 获取方法上的所有参数，返回Map类型, eg: 键："userName",值:xxx  键："password",值:xxx
     *
     * @param joinPoint
     * @return
     */
    public Map<String, Object> getParams(JoinPoint joinPoint) {
        Map<String, Object> params = new HashMap<>(8);
        // 通过切点获取方法所有参数值["zhangsan", "123456"]
        Object[] args = joinPoint.getArgs();
        // 通过切点获取方法所有参数名 eg:["userName", "password"]
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] names = signature.getParameterNames();
        for (int i = 0; i < args.length; i++) {
            params.put(names[i], args[i]);
        }
        return params;
    }

    /**
     * 从map中获取键为paramName的值，不存在放回null
     *
     * @param map
     * @param paramName
     * @return
     */
    private Object getValue(Map<String, Object> map, String paramName) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getKey().equals(paramName)) {
                return entry.getValue();
            }
        }
        return null;
    }
}
